﻿using System;
using System.Collections.Generic;
using System.Linq;


namespace advadev.DataStorage
{

    /// <summary>
    /// Represents a generic entity sql server data storage
    /// </summary>
    public class GenericEntitySqlServerDataStorage : IGenericEntityDataStorage
    {

        /// <summary>
        /// Initializes a new instance of the GenericEntitySqlServerDataStorage
        /// class by the given parameter
        /// </summary>
        /// <param name="connectionString">
        /// Connection string for the sql server
        /// </param>
        /// <exception cref="ArgumentException">
        /// Will be thrown if the connection string is not valid
        /// </exception>
        public GenericEntitySqlServerDataStorage(string connectionString)
        {
            try
            {
                DataContext = new GenericEntitiesDataContext(connectionString);
            }
            catch (Exception e)
            {
                throw new ArgumentException(e.Message, "connectionString");
            }
        }


        /// <summary>
        /// Gets or sets the data context
        /// </summary>
        private GenericEntitiesDataContext DataContext
        {
            get;
            set;
        }


        #region IGenericEntityDataStorage implementation
        /// <summary>
        /// Saves the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to save
        /// </param>
        public void Save(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            GenericDatabaseEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
            if (existingItem == null)
            {
                // -> insert
                GenericDatabaseEntity newItem = new GenericDatabaseEntity();
                newItem.ApplicationKey = genericEntity.ApplicationKey;
                newItem.FullClassName = genericEntity.FullClassName;
                newItem.EntityId = genericEntity.EntityId;
                newItem.XmlSerialization = genericEntity.XmlSerialization;
                DataContext.GenericDatabaseEntities.InsertOnSubmit(newItem);
            }
            else
            {
                // -> update
                existingItem.XmlSerialization = genericEntity.XmlSerialization;
            }
            DataContext.SubmitChanges();
        }

        /// <summary>
        /// Saves the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to save
        /// </param>
        public void Save(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            IList<GenericDatabaseEntity> insertedButNotSubmitted = new List<GenericDatabaseEntity>();
            foreach (GenericEntity genericEntity in genericEntities)
            {
                bool notSubmittedEntity = false;
                foreach (GenericDatabaseEntity notSubmitted in insertedButNotSubmitted)
                {
                    if (genericEntity.EntityId == notSubmitted.EntityId &&
                        genericEntity.ApplicationKey == notSubmitted.ApplicationKey &&
                        genericEntity.FullClassName == notSubmitted.FullClassName)
                    {
                        notSubmitted.XmlSerialization = genericEntity.XmlSerialization;
                        notSubmittedEntity = true;
                        break;
                    }
                }
                if (!notSubmittedEntity)
                {
                    GenericDatabaseEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
                    if (existingItem == null)
                    {
                        // -> insert
                        GenericDatabaseEntity newItem = new GenericDatabaseEntity();
                        newItem.ApplicationKey = genericEntity.ApplicationKey;
                        newItem.FullClassName = genericEntity.FullClassName;
                        newItem.EntityId = genericEntity.EntityId;
                        newItem.XmlSerialization = genericEntity.XmlSerialization;
                        DataContext.GenericDatabaseEntities.InsertOnSubmit(newItem);
                        insertedButNotSubmitted.Add(newItem);
                    }
                    else
                    {
                        // -> update
                        existingItem.XmlSerialization = genericEntity.XmlSerialization;
                        insertedButNotSubmitted.Add(existingItem);
                    }
                }
            }
            DataContext.SubmitChanges();
        }

        /// <summary>
        /// Finds the generic entity with the given id, application key and type
        /// </summary>
        /// <param name="id">
        /// Id of the generic entity
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// The generic entity or null
        /// </returns>
        public GenericEntity Find(string id, string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return null;
            }
            GenericDatabaseEntity existingItem = findExisting(id, applicationKey, fullClassName);
            if (existingItem != null)
            {
                GenericEntity genericEntity = new GenericEntity();
                genericEntity.ApplicationKey = existingItem.ApplicationKey;
                genericEntity.FullClassName = existingItem.FullClassName;
                genericEntity.EntityId = existingItem.EntityId;
                genericEntity.XmlSerialization = existingItem.XmlSerialization;
                return genericEntity;
            }
            return null;
        }

        /// <summary>
        /// Selects all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// A list of generic entities
        /// </returns>
        public IList<GenericEntity> Select(string applicationKey, string fullClassName)
        {
            List<GenericEntity> result = new List<GenericEntity>();
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return result;
            }
            var existingItems =
                from c in DataContext.GenericDatabaseEntities
                where (c.ApplicationKey.Equals(applicationKey) &&
                    c.FullClassName.Equals(fullClassName))
                orderby c.Id
                select c;
            if (existingItems != null)
            {
                foreach (var existingItem in existingItems)
                {
                    if (existingItem != null)
                    {
                        GenericEntity genericEntity = new GenericEntity();
                        genericEntity.ApplicationKey = existingItem.ApplicationKey;
                        genericEntity.FullClassName = existingItem.FullClassName;
                        genericEntity.EntityId = existingItem.EntityId;
                        genericEntity.XmlSerialization = existingItem.XmlSerialization;
                        result.Add(genericEntity);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Deletes the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to delete
        /// </param>
        public void Delete(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            GenericDatabaseEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
            if (existingItem != null)
            {
                DataContext.GenericDatabaseEntities.DeleteOnSubmit(existingItem);
                DataContext.SubmitChanges();
            }
        }

        /// <summary>
        /// Deletes the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to delete
        /// </param>
        public void Delete(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            bool changeOccured = false;
            foreach (GenericEntity genericEntity in genericEntities)
            {
                if (genericEntity != null)
                {
                    GenericDatabaseEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
                    if (existingItem != null)
                    {
                        DataContext.GenericDatabaseEntities.DeleteOnSubmit(existingItem);
                        changeOccured = true;
                    }
                }
            }
            if (changeOccured)
            {
                DataContext.SubmitChanges();
            }
        }

        /// <summary>
        /// Deletes all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        public void Delete(string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return;
            }
            bool changeOccured = false;
            var existingItems =
                from c in DataContext.GenericDatabaseEntities
                where (c.ApplicationKey.Equals(applicationKey) &&
                    c.FullClassName.Equals(fullClassName))
                select c;
            if (existingItems != null)
            {
                foreach (var existingItem in existingItems)
                {
                    DataContext.GenericDatabaseEntities.DeleteOnSubmit(existingItem);
                    changeOccured = true;
                }
            }
            if (changeOccured)
            {
                DataContext.SubmitChanges();
            }
        }

        /// <summary>
        /// Deletes all generic entities with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        public void Delete(string applicationKey)
        {
            if (string.IsNullOrEmpty(applicationKey))
            {
                return;
            }
            bool changeOccured = false;
            var existingItems =
                from c in DataContext.GenericDatabaseEntities
                where c.ApplicationKey.Equals(applicationKey)
                select c;
            if (existingItems != null)
            {
                foreach (var existingItem in existingItems)
                {
                    DataContext.GenericDatabaseEntities.DeleteOnSubmit(existingItem);
                    changeOccured = true;
                }
            }
            if (changeOccured)
            {
                DataContext.SubmitChanges();
            }
        }

        /// <summary>
        /// Finds all full class names with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <returns>
        /// List of full class names or empty list
        /// </returns>
        public IList<string> FindFullClassNames(string applicationKey)
        {
            IList<string> result = new List<string>();
            if (string.IsNullOrEmpty(applicationKey))
            {
                return result;
            }
            var fullClassNames =
                from c in DataContext.GenericDatabaseEntities
                where c.ApplicationKey == applicationKey
                select c.FullClassName;
            if (fullClassNames != null)
            {
                foreach (var fullClassName in fullClassNames)
                {
                    if (fullClassName != null)
                    {
                        if (!result.Contains(fullClassName))
                        {
                            result.Add(fullClassName);
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Finds all application keys
        /// </summary>
        /// <returns>
        /// List of application keys or empty list
        /// </returns>
        public IList<string> FindApplicationKeys()
        {
            IList<string> result = new List<string>();
            var applicationKeys =
                from c in DataContext.GenericDatabaseEntities
                select c.ApplicationKey;
            if (applicationKeys != null)
            {
                foreach (var applicationKey in applicationKeys)
                {
                    if (applicationKey != null)
                    {
                        if (!result.Contains(applicationKey))
                        {
                            result.Add(applicationKey);
                        }
                    }
                }
            }
            return result;
        }
        #endregion IGenericEntityDataStorage implementation


        #region helpers
        /// <summary>
        /// Finds a existing generic database entity
        /// </summary>
        /// <param name="entityId">
        /// Entity id
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name
        /// </param>
        /// <returns>
        /// Generic database entity or null
        /// </returns>
        private GenericDatabaseEntity findExisting(string entityId, string applicationKey, string fullClassName)
        {
            try
            {
                return DataContext.GenericDatabaseEntities.SingleOrDefault<GenericDatabaseEntity>(c => c.ApplicationKey == applicationKey && c.FullClassName == fullClassName && c.EntityId == entityId);
            }
            catch (InvalidOperationException)
            {
                // -> maybe identical entities (should never occure)
                var existingItems =
                    from c in DataContext.GenericDatabaseEntities
                    where (c.EntityId.Equals(entityId) &&
                        c.ApplicationKey.Equals(applicationKey) &&
                        c.FullClassName.Equals(fullClassName))
                    orderby c.Id
                    select c;
                if (existingItems != null)
                {
                    foreach (GenericDatabaseEntity existingItem in existingItems)
                    {
                        // -> return first found entity
                        return existingItem;
                    }
                }
            }
            return null;
        }
        #endregion helpers

    }

}
